# # CRAN
#
# install.packages("vegan")
# install.packages("Polychrome")
# install.packages("dendextend")
# install.packages("ggplotify")
# install.packages("parallelMap")
# install.packages("caret")
# install.packages("randomForest")
#
# # Bioconductor
#
# install.packages("BiocManager")
# BiocManager::install("phyloseq")
# BiocManager::install("ANCOMBC")
# BiocManager::install("mixOmics")
#
# # GitHub
#
# install.packages("devtools")
# devtools::install_github("jbisanz/qiime2R")
library(dplyr)
library(ggplot2)
Data is ASVs
# Required library
library(qiime2R)
library(phyloseq)
library(vegan)
# Source files:
feature_table_qza <- "feature_table.qza"
rooted_tree_qza <- "rooted_tree.qza"
taxonomy_qza <- "taxonomy.qza"
metadata_tsv <- "samples.txt"
# Read data
data.phy <- qza_to_phyloseq(
features = feature_table_qza,
tree = rooted_tree_qza,
taxonomy = taxonomy_qza,
metadata = metadata_tsv
)
# Cleanup
# Removal of not needed objects, packages and cleaning the RAM
rm(feature_table_qza, rooted_tree_qza, taxonomy_qza, metadata_tsv) # remove unnecessary objects
detach("package:qiime2R", unload=TRUE) # unload qiime2R package
gc() # free unused R memory
## used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
## Ncells 5097809 272.3 8215212 438.8 NA 8215212 438.8
## Vcells 29986376 228.8 88609525 676.1 16384 91947362 701.6
# Keep only samples with Industrial or Agricultural prior use
table(sample_data(data.phy)$Former_landuse)
##
## Agriculture Ancient_woodland Industrial Rewilding
## 150 20 150 10
IndAgri <- subset_samples(data.phy,
Former_landuse %in% c("Industrial","Agriculture"))
IndAgri
## phyloseq-class experiment-level object
## otu_table() OTU Table: [ 60355 taxa and 300 samples ]
## sample_data() Sample Data: [ 300 samples by 11 sample variables ]
## tax_table() Taxonomy Table: [ 60355 taxa by 7 taxonomic ranks ]
## phy_tree() Phylogenetic Tree: [ 60355 tips and 60236 internal nodes ]
table(as.data.frame(tax_table(data.phy))$Kingdom)
##
## d__Archaea d__Bacteria Unassigned
## 161 60173 21
# Keep only Bacteria
BacIndAgri <- subset_taxa(IndAgri, Kingdom == "d__Bacteria")
BacIndAgri # The OTU count dropped from 60,355 to 60,173
## phyloseq-class experiment-level object
## otu_table() OTU Table: [ 60173 taxa and 300 samples ]
## sample_data() Sample Data: [ 300 samples by 11 sample variables ]
## tax_table() Taxonomy Table: [ 60173 taxa by 7 taxonomic ranks ]
## phy_tree() Phylogenetic Tree: [ 60173 tips and 60054 internal nodes ]
plot_rarefaction_curve <- function(phy.obj, taxa_level) {
# sample depth Curve before Rarefaction
# Extract the ASV table from the phyloseq object
otu_table <- otu_table(phy.obj)
# Transpose the table
if (taxa_are_rows(otu_table)) {
otu_table <- t(otu_table)
}
# Convert to a matrix
otu_matrix <- as(otu_table, "matrix")
# Generate rarefaction curve to decide sample depth
rare_curve <- rarecurve(otu_matrix, step = 100, cex = 0.5, col = "blue", label = FALSE,
xlab = "Sample Size", ylab = taxa_level)
}
# Agglomerate Bacteria to family level
BacIndAgriFamily <- tax_glom(BacIndAgri, taxrank="Family")
BacIndAgriFamily
## phyloseq-class experiment-level object
## otu_table() OTU Table: [ 769 taxa and 300 samples ]
## sample_data() Sample Data: [ 300 samples by 11 sample variables ]
## tax_table() Taxonomy Table: [ 769 taxa by 7 taxonomic ranks ]
## phy_tree() Phylogenetic Tree: [ 769 tips and 768 internal nodes ]
# Check the sample depth for deciding the rarefaction threshold
plot_rarefaction_curve(BacIndAgriFamily, "ASVs (Family)")
# Agglomerate Bacteria to genus level
BacIndAgriGenera <- tax_glom(BacIndAgri, taxrank="Genus")
BacIndAgriGenera
## phyloseq-class experiment-level object
## otu_table() OTU Table: [ 1465 taxa and 300 samples ]
## sample_data() Sample Data: [ 300 samples by 11 sample variables ]
## tax_table() Taxonomy Table: [ 1465 taxa by 7 taxonomic ranks ]
## phy_tree() Phylogenetic Tree: [ 1465 tips and 1464 internal nodes ]
# Check the sample depth for deciding the rarefaction threshold
plot_rarefaction_curve(BacIndAgriGenera, "ASVs (Genus)")
# Check the sample depth for deciding the rarefaction threshold
plot_rarefaction_curve(BacIndAgri, "ASVs (Species)")
# Rarefy the phyloseq object to an even depth of 10000 sequences per sample
BacIndAgriGenera.rarefied <- rarefy_even_depth(BacIndAgriGenera,
sample.size = 10000,
rngseed = 123, replace = FALSE)
Rarefation done on Genera level, for multiple reasons: 1. not all ASVs are classified up to species 2. The 16srRNA sequencing achieves better genus-level resolution
The decision of sample depth threshold (10000) to use for normalization was mainly based on visual assessment of two things: sample reaching the plateau of ASVs count and avoiding losing any samples.
# (Function) to create and save a histogram
save_histogram <- function(data, xlab, ylab, main, filename) {
# Display the histogram
hist(data, xlab = xlab, ylab = ylab, main = main)
# Save the histogram to a file
dev.copy(png, filename)
dev.off()
}
# Convert the OTU table to matrix
BacIndAgriGenera.numeric <- as.numeric(otu_table(BacIndAgriGenera))
BacIndAgriGenera.mtx <- matrix(BacIndAgriGenera.numeric, nrow=nrow(otu_table(BacIndAgriGenera)))
# Getting the ASV that has more than 0 count in each sample THRESHOLD
BacIndAgriGenera.count_filter <- BacIndAgriGenera.mtx > 0
# Sum the number of ASV for each sample that has > count threshold
# The result will show the number of ASV (frequency, y-axis) > count threshold in each sample (x-axis)
BacIndAgriGenera.count_filter.sum <- apply(BacIndAgriGenera.count_filter, 1, sum)
# Display and save the first histogram
save_histogram(BacIndAgriGenera.count_filter.sum, xlab = "Samples", ylab = "ASVs count (Genera)",
main = "Frequency of ASVs with count > 0 across samples",
filename = "results/filter_low_quality_ASVs/ASVsGenera_count_histogram.png")
## quartz_off_screen
## 2
# Plot histogram for sample interval (0-10)
BacIndAgriGenera.count_filter.sum.1_10.interval <- BacIndAgriGenera.count_filter.sum[BacIndAgriGenera.count_filter.sum < 10]
# Display and save the second histogram
save_histogram(BacIndAgriGenera.count_filter.sum.1_10.interval, xlab = "Samples", ylab = "ASVs count (Genera)",
main = "Frequency of ASVs with count > 0 in 10 samples or less",
filename = "results/filter_low_quality_ASVs/ASVsGenera_count_histogram_1_10_interval.png")
## quartz_off_screen
## 2
Interpretation: Let’s say for the second plot we have ~ 150 ASVs that are present in two samples.
all_taxa.num <- length(BacIndAgriGenera.count_filter.sum)
taxa_less10samples <- length(BacIndAgriGenera.count_filter.sum.1_10.interval)
lost_taxa_perc <- round((taxa_less10samples / all_taxa.num) * 100, 2)
print(paste("Out of", all_taxa.num, "the number of lost ASVs (Genera) after filtering taxa with abundance > 0 counts in less than 10 samples", taxa_less10samples, "Which is", lost_taxa_perc, "%"))
## [1] "Out of 1465 the number of lost ASVs (Genera) after filtering taxa with abundance > 0 counts in less than 10 samples 766 Which is 52.29 %"
From count filtering histogram and stats, half of ASVs (Genera) will be lost. Therefore, for further analysis the data will be kept without filtering unless it’s required for a statistical method to be used.
library(ggplot2)
library(plotly)
library(RColorBrewer)
# Aggregate data at Phylum level
BacIndAgriPhylum.rarefied <- tax_glom(BacIndAgriGenera.rarefied, taxrank = "Phylum")
env_factor_bar_plot <- function(phyloseq.obj, env_factor, save_path, levels=c()) {
# Merge samples by environmental factor (categorical one)
phyloseq.obj.merged <- merge_samples(phyloseq.obj, env_factor)
# Transform counts to relative abundances
phyloseq.obj.merged <- transform_sample_counts(phyloseq.obj.merged, function(x) x / sum(x))
# Extract the data for plotting
phyloseq.obj.merged.df <- psmelt(phyloseq.obj.merged)
# Get counts per phylum
Phyla.df <- phyloseq.obj.merged.df %>%
group_by(Phylum) %>%
summarise(Count = sum(Abundance))
# Select the cut-off for the Phylum taxa (e.g. 1% of total count)
cutoff <- 0.01 * sum(phyloseq.obj.merged.df$Abundance)
# Select low-abundant Phyla (with total counts below the cutoff)
lowAbundant <- Phyla.df[Phyla.df$Count <= cutoff,]$Phylum
# Substitute Phylum names to "<1%" for the low-abundant phyla
phyloseq.obj.merged.df[phyloseq.obj.merged.df$Phylum %in% lowAbundant,]$Phylum <- '<1%'
# Ensure env_factor levels are ordered correctly in the melted data frame
if (length(levels) > 0){
phyloseq.obj.merged.df$Sample <- factor(phyloseq.obj.merged.df$Sample, levels = levels)
}
# Define high contrast palette for bar plot
n_colors <- length(unique(phyloseq.obj.merged.df$Phylum))
high_contrast_palette <- c(brewer.pal(min(9, n_colors), "Set1"), brewer.pal(max(0, n_colors - 9), "Dark2"))
# Create bar plot
barplot <- ggplot(phyloseq.obj.merged.df, aes(x = Sample, y = Abundance, fill = Phylum, text = paste("Phylum:", Phylum, "<br>", env_factor, ":", Sample, "<br>Relative Abundance:", Abundance))) +
geom_bar(stat = "identity", position = "stack") +
scale_fill_manual(values = high_contrast_palette) +
theme(axis.text.x = element_text(angle = 0, hjust = 1)) +
labs(title = paste("Taxa Abundance by", env_factor), x = env_factor, y = "Relative Abundance")
# Convert to plotly
barplot.plotly <- ggplotly(barplot, tooltip = "text")
barplot.plotly
# Save the plot as an HTML file
htmlwidgets::saveWidget(barplot.plotly, save_path)
return(barplot.plotly)
}
barplot.FLU <- env_factor_bar_plot(phyloseq.obj = BacIndAgriPhylum.rarefied, env_factor = "Former_landuse",
save_path = "results/plotly/Taxonomy_Phyla_landuse_normalized.html")
barplot.FLU
pHValues <- as.numeric(sample_data(BacIndAgriGenera)$pH)
## Warning: NAs introduced by coercion
pHValues.removed_nan <- na.omit(pHValues)
print("")
## [1] ""
print("max pH")
## [1] "max pH"
max(pHValues.removed_nan)
## [1] 7.98
print("Min pH")
## [1] "Min pH"
min(pHValues.removed_nan)
## [1] 3.78
print("Median pH")
## [1] "Median pH"
median(pHValues.removed_nan)
## [1] 5.54
# Make sure pH column is numeric
sample_data(BacIndAgriPhylum.rarefied)$pH <- as.numeric(as.character(sample_data(BacIndAgriPhylum.rarefied)$pH))
# Categorizing pH
sample_data(BacIndAgriPhylum.rarefied)$pH_category <- factor(
cut(sample_data(BacIndAgriPhylum.rarefied)$pH,
breaks = c(3.78, 5, 6, 7.98),
labels = c("<5", "5-6", ">6")),
levels = c("<5", "5-6", ">6")
)
pH_less_5 <- as.numeric(sample_data(BacIndAgriPhylum.rarefied)$pH_category == "<5")
pH_less_5 <- na.omit(pH_less_5)
print(paste("Number of samples with to pH < 5 =", sum(pH_less_5)))
## [1] "Number of samples with to pH < 5 = 50"
pH_5_6 <- as.numeric(sample_data(BacIndAgriPhylum.rarefied)$pH_category == "5-6")
pH_5_6 <- na.omit(pH_5_6)
print(paste("Number of samples with to pH 5-6 =", sum(pH_5_6)))
## [1] "Number of samples with to pH 5-6 = 145"
pH_more_6 <- as.numeric(sample_data(BacIndAgriPhylum.rarefied)$pH_category == ">6")
pH_more_6 <- na.omit(pH_more_6)
print(paste("Number of samples with pH >6 =", sum(pH_more_6)))
## [1] "Number of samples with pH >6 = 95"
barplot.pH <- env_factor_bar_plot(phyloseq.obj = BacIndAgriPhylum.rarefied, env_factor = "pH_category",
save_path = "results/plotly/Taxonomy_Phyla_pH_normalized.html",
levels = c("<5", "5-6", ">6"))
barplot.pH
conductivityValues <- as.numeric(sample_data(BacIndAgriGenera)$Conductivity)
## Warning: NAs introduced by coercion
# Calculate the quartiles
quartiles <- quantile(conductivityValues, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)
quartiles
## 25% 50% 75%
## 39 64 134
# Make sure conductivity column is numeric
sample_data(BacIndAgriPhylum.rarefied)$Conductivity <- as.numeric(as.character(sample_data(BacIndAgriPhylum.rarefied)$Conductivity))
# Calculate the quartiles for conductivity
quartiles <- quantile(sample_data(BacIndAgriPhylum.rarefied)$Conductivity, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)
# Categories based on quartiles
sample_data(BacIndAgriPhylum.rarefied)$Conductivity_category <-
cut(sample_data(BacIndAgriPhylum.rarefied)$Conductivity,
breaks = c(-Inf, quartiles, Inf),
labels = c("Q1: <0.25", "Q2: 0.25-0.5", "Q3: 0.5-0.75", "Q4: 0.75<"),
include.lowest = TRUE)
barplot.conductivity <- env_factor_bar_plot(phyloseq.obj = BacIndAgriPhylum.rarefied, env_factor = "Conductivity_category",
save_path = "results/plotly/Taxonomy_Phyla_Conductivity_normalized.html")
barplot.conductivity
# Make sure pH column is numeric
sample_data(BacIndAgri)$pH <- as.numeric(as.character(sample_data(BacIndAgri)$pH))
# Make Former_landuse column as factor
sample_data(BacIndAgri)$Former_landuse <- as.factor(sample_data(BacIndAgri)$Former_landuse)
# Extract sample data into a df
FLU_pH.df <- data.frame(sample_data(BacIndAgri))
FLU_pH.plot <- plot_ly()
# Compute density estimates for each Former_landuse level
for(level in levels(FLU_pH.df$Former_landuse)) {
data_subset <- FLU_pH.df[FLU_pH.df$Former_landuse == level, ]
dens <- density(data_subset$pH, na.rm = TRUE) # Compute density
# Add density trace
FLU_pH.plot <- FLU_pH.plot %>% add_trace(
x = dens$x,
y = dens$y,
type = 'scatter',
mode = 'lines',
name = level,
opacity = 0.6
)
}
# Customize the layout
FLU_pH.plot <- FLU_pH.plot %>% layout(
title = "Kernel Density Estimate of pH by Former Landuse",
xaxis = list(title = "pH"),
yaxis = list(title = "Density")
)
FLU_pH.plot
# Save the plot as an HTML file
htmlwidgets::saveWidget(FLU_pH.plot, "results/plotly/pH_vs_FLU.html")
Scatter Plot ### pH vs Conductivity
library(broom)
# Make sure conductivity column is numeric
sample_data(BacIndAgri)$Conductivity <- as.numeric(as.character(sample_data(BacIndAgri)$Conductivity))
## Warning: NAs introduced by coercion
# Extract sample data
Cond_pH.df <- data.frame(sample_data(BacIndAgri))
# linear regression
model <- lm(Conductivity ~ pH, data = Cond_pH.df)
# Extract the coefficients
summary_lm <- summary(model) # Benjamini-Hochberg adjusted p-value by default
print(summary_lm)
##
## Call:
## lm(formula = Conductivity ~ pH, data = Cond_pH.df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -85.67 -37.37 -11.57 29.63 237.12
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -96.885 20.258 -4.783 2.74e-06 ***
## pH 31.986 3.474 9.207 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 56.62 on 293 degrees of freedom
## (5 observations deleted due to missingness)
## Multiple R-squared: 0.2244, Adjusted R-squared: 0.2218
## F-statistic: 84.77 on 1 and 293 DF, p-value: < 2.2e-16
# Extract R-squared and slope
r_squared <- summary_lm$r.squared
slope <- summary_lm$coefficients[2, 1]
# Create the regression plot
Cond_pH.plot <- ggplot(Cond_pH.df, aes(x = pH, y = Conductivity, color = Former_landuse)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Regression of Conductivity on pH by Former Land Use",
subtitle = paste("R-squared =", round(r_squared, 2),
"Slope =", round(slope, 2)),
x = "pH",
y = "Conductivity") +
theme_minimal()
print(Cond_pH.plot)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).
# Convert to plotly
Cond_pH.plot_plotly <- ggplotly(Cond_pH.plot)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range
## (`stat_smooth()`).
print(Cond_pH.plot_plotly)
# Save the plot as an HTML file
htmlwidgets::saveWidget(Cond_pH.plot_plotly, "results/plotly/Regression_pH_Conductivity_by_LandUse.html")
Adjusted R-squared 0.2218 (very low) shows that the model failed to explain the real data points and the results are not reliable. For the results it tells that for one unit increase of pH the conductivity increases by 31.986, and theoretically at pH 0 the conductivity is -96.885
library(dplyr)
# Function to compute the correlation between microbial abundance and environmental factor
abundance_env_factor_corr <- function(phyloseq.obj, env_factor){
# Create abundance df
phyloseq.obj.abund <- psmelt(phyloseq.obj)
# Ensure environmental factors are numerical
phyloseq.obj.abund$Woodland_age <- as.numeric(as.character(phyloseq.obj.abund$Woodland_age))
phyloseq.obj.abund$Conductivity <- as.numeric(as.character(phyloseq.obj.abund$Conductivity))
phyloseq.obj.abund$pH <- as.numeric(as.character(phyloseq.obj.abund$pH))
# Aggregate abundance by Site_code (as samples of each site share the same value of pH, Conductivity, age) and Phylum, summing ASVs
phyloseq.obj.abund.summary <- phyloseq.obj.abund %>%
group_by(Site_code, Phylum) %>%
summarise(Abundance = sum(Abundance), .groups = 'drop')
# Align Sample IDs between phyloseq sample data and the summary data
sample_data_df <- as.data.frame(sample_data(phyloseq.obj))
sample_data_df$Sample <- rownames(sample_data_df)
# Join the summary data with the sample metadata
phyloseq.obj.abund.summary <- left_join(phyloseq.obj.abund.summary, sample_data_df, by = "Site_code")
# Correlation function
compute_correlation <- function(df) {
cor_result <- cor.test(df[[env_factor]], df$Abundance, method = "spearman")
data.frame(
corr = cor_result$estimate,
p_value = cor_result$p.value
)
}
# Compute correlation and p-value for each Phylum
correlations <- phyloseq.obj.abund.summary %>%
group_by(Phylum) %>%
summarise(correlation = list(compute_correlation(cur_data())), .groups = 'drop')
# Flatten the list column to extract correlation and p-value
correlations <- correlations %>%
mutate(
correlation = sapply(correlations$correlation, function(x) x$corr),
p_value = sapply(correlations$correlation, function(x) x$p_value)
)
# Calculate the adjusted p-value
correlations$pAdjust <- p.adjust(correlations$p_value)
# Filter taxa based on adjusted p-value <= 0.05
significant_taxa <- correlations %>%
filter(pAdjust <= 0.05)
# Get top positively and negatively correlated taxa
top_positive <- significant_taxa %>%
arrange(desc(correlation)) %>%
slice_head(n = 4)
top_negative <- significant_taxa %>%
arrange(correlation) %>%
slice_head(n = 4)
print("Top positively correlated taxa with Woodland age:")
print(top_positive)
print("Top negatively correlated taxa with Woodland age:")
print(top_negative)
return(list("abund.summary"=phyloseq.obj.abund.summary,
"top_positive"=top_positive, "top_negative"=top_negative))
}
age_abund_corr <- abundance_env_factor_corr(phyloseq.obj = BacIndAgriPhylum.rarefied,
env_factor = "Woodland_age")
## Warning in left_join(phyloseq.obj.abund.summary, sample_data_df, by = "Site_code"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 146 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
## Warning: There were 58 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `correlation = list(compute_correlation(cur_data()))`.
## ℹ In group 1: `Phylum = "Acidobacteriota"`.
## Caused by warning:
## ! `cur_data()` was deprecated in dplyr 1.1.0.
## ℹ Please use `pick()` instead.
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 57 remaining warnings.
## [1] "Top positively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 CSP1-3 0.370 3.70e-11 0.00000000211
## 2 Sumerlaeota 0.304 7.64e- 8 0.00000428
## 3 Desulfobacterota_C 0.299 1.29e- 7 0.00000707
## 4 KSB1 0.283 6.44e- 7 0.0000348
## [1] "Top negatively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 Firmicutes_E -0.278 0.00000104 0.0000552
## 2 Firmicutes_A -0.261 0.00000468 0.000234
## 3 Firmicutes_B_370531 -0.222 0.000106 0.00454
## 4 Planctomycetota -0.199 0.000520 0.0218
top_pos_neg_plot <- function(top_positive, top_negative, abund.summary, env_factor) {
# Select the top positively and negatively correlated taxa
top_taxa <- rbind(top_positive[1, ], top_negative[1, ])
# Extract data for these taxa
top_taxa.abund.summary <- abund.summary %>%
filter(Phylum %in% top_taxa$Phylum)
# Scatter plots
for (phylum in unique(top_taxa$Phylum)) {
# Filter data for the current Phylum
phylum_data <- top_taxa.abund.summary %>% filter(Phylum == phylum)
# Fit a linear model
lm_formula <- paste("Abundance", "~", env_factor)
lm_formula <- as.formula(lm_formula)
lm_model <- lm(lm_formula, data = phylum_data)
summary_lm <- summary(lm_model)
print("--------------------------------------")
print(summary_lm)
# Extract R-squared and slope
r_squared <- summary_lm$r.squared
slope <- summary_lm$coefficients[2, 1]
# Create scatter plot
p <- ggplot(phylum_data, aes_string(x = env_factor, y = "Abundance")) +
geom_point(color = "blue") +
geom_smooth(method = "lm", se = TRUE, color = "red") +
labs(title = paste("Abundance of", phylum, "vs", env_factor),
subtitle = paste("R-squared =", round(r_squared, 2),
"Slope =", round(slope, 2)),
x = env_factor,
y = "Abundance") +
theme_minimal()
# Print the plot
print(p)
}
}
top_pos_neg_plot(top_positive = age_abund_corr$top_positive, top_negative = age_abund_corr$top_negative,
abund.summary = age_abund_corr$abund.summary, env_factor = "Woodland_age")
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.3521 -0.5442 -0.1128 0.1539 4.9616
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.750057 0.174707 -4.293 2.38e-05 ***
## Woodland_age 0.031376 0.004255 7.373 1.65e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.196 on 298 degrees of freedom
## Multiple R-squared: 0.1543, Adjusted R-squared: 0.1515
## F-statistic: 54.37 on 1 and 298 DF, p-value: 1.654e-12
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.8395 -1.8849 -1.0357 -0.2165 23.9643
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.48259 0.58965 5.906 9.54e-09 ***
## Woodland_age -0.04019 0.01436 -2.799 0.00547 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.035 on 298 degrees of freedom
## Multiple R-squared: 0.02561, Adjusted R-squared: 0.02234
## F-statistic: 7.832 on 1 and 298 DF, p-value: 0.005467
## `geom_smooth()` using formula = 'y ~ x'
pH_abund_corr <- abundance_env_factor_corr(phyloseq.obj = BacIndAgriPhylum.rarefied,
env_factor = "pH")
## Warning in left_join(phyloseq.obj.abund.summary, sample_data_df, by = "Site_code"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 146 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
## Warning: There were 57 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `correlation = list(compute_correlation(cur_data()))`.
## ℹ In group 1: `Phylum = "Acidobacteriota"`.
## Caused by warning in `cor.test.default()`:
## ! Cannot compute exact p-value with ties
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 56 remaining warnings.
## [1] "Top positively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 Chloroflexota 0.699 1.34e-44 7.65e-43
## 2 Methylomirabilota 0.506 1.53e-20 8.10e-19
## 3 Myxococcota_A_437813 0.438 3.02e-15 1.51e-13
## 4 Myxococcota_A_473307 0.425 2.25e-14 1.10e-12
## [1] "Top negatively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 Eremiobacterota -0.678 5.35e-41 2.99e-39
## 2 Planctomycetota -0.675 1.23e-40 6.78e-39
## 3 Acidobacteriota -0.559 1.17e-25 6.31e-24
## 4 Firmicutes_D -0.505 1.58e-20 8.20e-19
top_pos_neg_plot(top_positive = pH_abund_corr$top_positive, top_negative = pH_abund_corr$top_negative,
abund.summary = pH_abund_corr$abund.summary, env_factor = "pH")
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1367.81 -478.40 -41.78 346.47 2487.36
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1003.86 270.30 -3.714 0.000244 ***
## pH 530.33 46.35 11.441 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 755.4 on 293 degrees of freedom
## (5 observations deleted due to missingness)
## Multiple R-squared: 0.3088, Adjusted R-squared: 0.3064
## F-statistic: 130.9 on 1 and 293 DF, p-value: < 2.2e-16
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -68.52 -40.64 -11.69 27.74 244.41
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 293.099 20.634 14.20 <2e-16 ***
## pH -41.186 3.539 -11.64 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 57.67 on 293 degrees of freedom
## (5 observations deleted due to missingness)
## Multiple R-squared: 0.3162, Adjusted R-squared: 0.3138
## F-statistic: 135.5 on 1 and 293 DF, p-value: < 2.2e-16
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range (`stat_smooth()`).
## Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).
conductivity_abund_corr <- abundance_env_factor_corr(phyloseq.obj = BacIndAgriPhylum.rarefied,
env_factor = "Conductivity")
## Warning in left_join(phyloseq.obj.abund.summary, sample_data_df, by = "Site_code"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 146 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
## Warning: There were 57 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `correlation = list(compute_correlation(cur_data()))`.
## ℹ In group 1: `Phylum = "Acidobacteriota"`.
## Caused by warning in `cor.test.default()`:
## ! Cannot compute exact p-value with ties
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 56 remaining warnings.
## [1] "Top positively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 Actinobacteriota 0.699 1.69e-44 9.66e-43
## 2 Chloroflexota 0.546 2.66e-24 1.33e-22
## 3 Firmicutes_B_370539 0.454 2.16e-16 9.94e-15
## 4 Methylomirabilota 0.415 9.78e-14 4.40e-12
## [1] "Top negatively correlated taxa with Woodland age:"
## # A tibble: 4 × 4
## Phylum correlation p_value pAdjust
## <chr> <dbl> <dbl> <dbl>
## 1 Acidobacteriota -0.678 4.25e-41 2.38e-39
## 2 FCPU426 -0.677 5.91e-41 3.25e-39
## 3 Elusimicrobiota -0.639 3.03e-35 1.64e-33
## 4 Armatimonadota -0.632 2.47e-34 1.31e-32
top_pos_neg_plot(top_positive = conductivity_abund_corr$top_positive,
top_negative = conductivity_abund_corr$top_negative,
abund.summary = conductivity_abund_corr$abund.summary, env_factor = "Conductivity")
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -10202.1 -3013.9 -482.6 2152.7 13087.8
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 7162.405 402.288 17.80 <2e-16 ***
## Conductivity 46.899 3.719 12.61 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4093 on 293 degrees of freedom
## (5 observations deleted due to missingness)
## Multiple R-squared: 0.3518, Adjusted R-squared: 0.3496
## F-statistic: 159 on 1 and 293 DF, p-value: < 2.2e-16
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).
## [1] "--------------------------------------"
##
## Call:
## lm(formula = lm_formula, data = phylum_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6348.6 -1759.1 -91.6 1591.9 7329.7
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 13005.879 262.183 49.61 <2e-16 ***
## Conductivity -27.651 2.424 -11.41 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2667 on 293 degrees of freedom
## (5 observations deleted due to missingness)
## Multiple R-squared: 0.3075, Adjusted R-squared: 0.3052
## F-statistic: 130.1 on 1 and 293 DF, p-value: < 2.2e-16
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5 rows containing non-finite outside the scale range (`stat_smooth()`).
## Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).
alpha_diversity.plot <- function(alpha_richness, phyloseq_obj, env_factor){
# Apply Wilcoxon test
chao1_res <- wilcox.test(alpha_richness$Chao1~
sample_data(phyloseq_obj)[[env_factor]])
chao1.p_value <- chao1_res$p.value
shannon_res <- wilcox.test(alpha_richness$Shannon~
sample_data(phyloseq_obj)[[env_factor]])
shannon.p_value <- shannon_res$p.value
simpson_res <- wilcox.test(alpha_richness$Simpson~
sample_data(phyloseq_obj)[[env_factor]])
simpson.p_value <- simpson_res$p.value
plot_richness(phyloseq_obj,
measures=c("Chao1", "Shannon", "Simpson"),
color=env_factor, x=env_factor) +
geom_boxplot() + ggtitle(paste("Alpha diversity vs", env_factor),
subtitle=paste("Chao1 p-value:", chao1.p_value,
"\nShannon p-value:", shannon.p_value,
"\nSimpson p-value:", simpson.p_value))
}
# Calculate Alpha diversity indices
alpha_richness = estimate_richness(
BacIndAgriGenera.rarefied, measures = c("Chao1", "Shannon", "Simpson"))
alpha_diversity.plot(alpha_richness = alpha_richness, phyloseq_obj = BacIndAgriGenera.rarefied,
env_factor = "Former_landuse")
## by Region
alpha_diversity.plot(alpha_richness = alpha_richness, phyloseq_obj = BacIndAgriGenera.rarefied,
env_factor = "Region")
library(ANCOMBC)
Differential Abundance Tests: pH, conductivity, Region, age, FLU
pH + conductivity + age –> Trend test Region –> Global test / pairwise FLU –> pairwise
# Estimate differential abundances
# 1. FLU (Pairwise) bonferroni correction is often applied to control for the increased risk of Type I errors
ancom.FLU = ancombc2(data = BacIndAgriGenera.rarefied, tax_level = "Genus",
fix_formula = "Former_landuse",
group = "Former_landuse",
p_adj_method = "bonferroni", alpha = 0.05, pairwise = TRUE,
prv_cut = 0.10, lib_cut = 0,
n_cl = 4, verbose = TRUE)
# alpha: significance cut-off
# prv_cut: filter taxa not present in prv_cut * samples_number
# lib_cut: filter samples with library size threshold
# Extract the results table
ancom.FLU.df <- ancom.FLU$res
colnames(ancom.FLU.df)
## [1] "taxon" "lfc_(Intercept)"
## [3] "lfc_Former_landuseIndustrial" "se_(Intercept)"
## [5] "se_Former_landuseIndustrial" "W_(Intercept)"
## [7] "W_Former_landuseIndustrial" "p_(Intercept)"
## [9] "p_Former_landuseIndustrial" "q_(Intercept)"
## [11] "q_Former_landuseIndustrial" "diff_(Intercept)"
## [13] "diff_Former_landuseIndustrial" "passed_ss_(Intercept)"
## [15] "passed_ss_Former_landuseIndustrial"
# Select only columns that we need
ancom.FLU.df <- ancom.FLU.df %>%
select(taxon, contains("Former_landuse"))
# Select differentially abundant taxa
dif.FLU.df <- ancom.FLU.df %>%
filter(diff_Former_landuseIndustrial & passed_ss_Former_landuseIndustrial) %>%
arrange(desc(lfc_Former_landuseIndustrial))
# Filter out values that are not 2 fold change in both positive and negative direction
dif.FLU.df <- dif.FLU.df %>%
filter(lfc_Former_landuseIndustrial >= 1 | lfc_Former_landuseIndustrial <= -1)
# Create the 'direct' column to categorize LFC
dif.FLU.df <- dif.FLU.df %>%
mutate(
direct = ifelse(lfc_Former_landuseIndustrial >= 1, "Positive LFC", "Negative LFC")
)
# Ensure direct factorized
dif.FLU.df$direct =
factor(dif.FLU.df$direct, levels = c("Positive LFC", "Negative LFC"))
dif.FLU.df$taxon =
factor(dif.FLU.df$taxon, levels = dif.FLU.df$taxon)
dif.FLU.df
## taxon lfc_Former_landuseIndustrial se_Former_landuseIndustrial
## 1 VAYN01 1.067419 0.11055884
## 2 Clostridium_AD -1.004158 0.09876164
## 3 Acidoferrum -1.110790 0.14766042
## 4 AV80 -1.308830 0.10030112
## 5 Sporosarcina -1.320468 0.11265035
## W_Former_landuseIndustrial p_Former_landuseIndustrial
## 1 9.654758 3.372546e-19
## 2 -10.167493 1.018458e-20
## 3 -7.522598 7.466592e-13
## 4 -13.049009 2.050293e-30
## 5 -11.721832 1.943810e-23
## q_Former_landuseIndustrial diff_Former_landuseIndustrial
## 1 1.689646e-16 TRUE
## 2 5.102475e-18 TRUE
## 3 3.740763e-10 TRUE
## 4 1.027197e-27 TRUE
## 5 9.738489e-21 TRUE
## passed_ss_Former_landuseIndustrial direct
## 1 TRUE Positive LFC
## 2 TRUE Negative LFC
## 3 TRUE Negative LFC
## 4 TRUE Negative LFC
## 5 TRUE Negative LFC
# Make bar plot
dif.FLU.plot <- dif.FLU.df %>%
ggplot(aes(x = taxon, y = lfc_Former_landuseIndustrial, fill = direct)) +
geom_bar(stat = "identity", width = 0.7, color = "black",
position = position_dodge(width = 0.4)) +
geom_errorbar(aes(ymin = lfc_Former_landuseIndustrial - se_Former_landuseIndustrial,
ymax = lfc_Former_landuseIndustrial + se_Former_landuseIndustrial),
width = 0.2, position = position_dodge(0.05),
color = "black") +
labs(x = "Genus", y = "Log fold change") +
ggtitle(label = "Differentially abundant taxa",
subtitle="Prior land use: Industrial vs Agricultural") +
scale_fill_discrete(name = NULL) +
scale_color_discrete(name = NULL) +
theme_bw() +
theme(panel.grid.minor.y = element_blank(),
axis.text.x = element_text(size = 6, angle = 60, hjust = 1))
# Convert to plotly
dif.FLU.plot_plotly <- ggplotly(dif.FLU.plot, tooltip = "text")
dif.FLU.plot_plotly
# Save the plot as an HTML file
htmlwidgets::saveWidget(dif.FLU.plot_plotly, "results/plotly/differential_abundant_taxa_FLU.html")
# box plot function
diff_abund.box_plot <- function(taxon, env_factor, title) {
taxon_data <- psmelt(BacIndAgriGenera.rarefied) %>%
filter(Genus == taxon)
plot <- ggplot(taxon_data, aes_string(x = env_factor, y = "Abundance", fill = env_factor)) +
geom_boxplot() +
geom_jitter(width = 0.2, alpha = 0.5) +
labs(title = title, x = env_factor, y = "Abundance") +
theme_minimal() +
theme(legend.position = "none")
return(plot)
}
# Extract the first positive and negative taxa
first_positive_taxa.FLU <- dif.FLU.df %>%
filter(direct == "Positive LFC") %>%
slice(1) %>%
pull(taxon)
first_negative_taxa.FLU <- dif.FLU.df %>%
filter(direct == "Negative LFC") %>%
slice(1) %>%
pull(taxon)
# Generate and plot the box plots
positive_plot.FLU <- diff_abund.box_plot(first_positive_taxa.FLU, "Former_landuse",
paste("Abundance of", first_positive_taxa.FLU, "by Former Land Use"))
negative_plot.FLU <- diff_abund.box_plot(first_negative_taxa.FLU, "Former_landuse",
paste("Abundance of", first_negative_taxa.FLU, "by Former Land Use"))
print(positive_plot.FLU)
print(negative_plot.FLU)
# 2. Region
ancom.region = ancombc2(data = BacIndAgriGenera.rarefied, tax_level = "Genus",
fix_formula = "Region",
group = "Region",
p_adj_method = "bonferroni", alpha = 0.05, pairwise = TRUE,
prv_cut = 0.10, lib_cut = 0,
n_cl = 4, verbose = TRUE)
# Extract the results table
ancom.region.df <- ancom.region$res
# Select only columns that we need
ancom.region.df <- ancom.region.df %>%
select(taxon, contains("Region"))
# Select differentially abundant taxa
dif.region.df <- ancom.region.df %>%
filter(diff_RegionScotland & passed_ss_RegionScotland) %>%
arrange(desc(lfc_RegionScotland))
sum(dif.region.df$lfc_RegionScotland > 1)
## [1] 11
# Filter out values that are not 2 fold change in both positive and negative direction
dif.region.df <- dif.region.df %>%
filter(lfc_RegionScotland >= 1 | lfc_RegionScotland <= -1)
# Create the 'direct' column to categorize LFC
dif.region.df <- dif.region.df %>%
mutate(
direct = ifelse(lfc_RegionScotland >= 1, "Positive LFC", "Negative LFC")
)
# Ensure direct factorized
dif.region.df$direct =
factor(dif.region.df$direct, levels = c("Positive LFC", "Negative LFC"))
dif.region.df$taxon =
factor(dif.region.df$taxon, levels = dif.region.df$taxon)
dif.region.df
## taxon lfc_RegionScotland se_RegionScotland W_RegionScotland
## 1 EW11 1.577992 0.08073215 19.546019
## 2 Palsa-189 1.231314 0.13282206 9.270407
## 3 Palsa-872 1.214490 0.08660293 14.023654
## 4 Gp1-AA122 1.211490 0.13711710 8.835439
## 5 Acidoferrum 1.190643 0.15313738 7.774999
## 6 Palsa-187 1.176684 0.12307774 9.560496
## 7 Pelomicrobium 1.119011 0.10767336 10.392644
## 8 UBA8199 1.073434 0.13952468 7.693504
## 9 CADDZI01 1.070957 0.10605152 10.098461
## 10 Gp1-AA133 1.037316 0.10579115 9.805321
## 11 Chitinophaga 1.030494 0.09712028 10.610493
## 12 Methyloligella -1.016611 0.08253043 -12.318017
## 13 HRBIN40 -1.024805 0.12729827 -8.050426
## 14 Povalibacter -1.064624 0.12325549 -8.637539
## 15 SHVA01 -1.098300 0.12047777 -9.116201
## 16 Actinoplanes -1.143953 0.10749038 -10.642381
## 17 Iso899 -1.173462 0.08813204 -13.314820
## 18 Skermanella -1.180881 0.08774098 -13.458715
## 19 Luteitalea -1.197843 0.13904031 -8.615079
## 20 Microlunatus_A_391020 -1.206870 0.11513235 -10.482460
## 21 Streptomyces_G_399870 -1.253952 0.09705535 -12.919970
## 22 Solirubrobacter -1.283276 0.12757874 -10.058696
## 23 Blastococcus -1.304384 0.08266749 -15.778683
## 24 JAAAPF01 -1.321297 0.09583837 -13.786724
## 25 Lysobacter_A_615995 -1.330093 0.08674484 -15.333400
## 26 VFJN01 -1.440884 0.12486523 -11.539513
## 27 Ohtaekwangia -1.504922 0.13474903 -11.168333
## 28 Kribbella -1.546404 0.11386899 -13.580558
## 29 Actinophytocola -1.581917 0.08886309 -17.801735
## 30 Nocardioides_A_392796 -1.616094 0.11867816 -13.617449
## 31 AC-51 -1.624315 0.13338625 -12.177530
## 32 GMQP-bins7 -1.651709 0.14174753 -11.652475
## p_RegionScotland q_RegionScotland diff_RegionScotland
## 1 3.703292e-26 1.855349e-23 TRUE
## 2 1.017336e-17 5.096852e-15 TRUE
## 3 3.163027e-18 1.584677e-15 TRUE
## 4 1.157081e-16 5.796977e-14 TRUE
## 5 1.476011e-13 7.394815e-11 TRUE
## 6 5.882774e-19 2.947270e-16 TRUE
## 7 1.681113e-21 8.422374e-19 TRUE
## 8 5.030266e-13 2.520163e-10 TRUE
## 9 1.784083e-16 8.938257e-14 TRUE
## 10 1.906488e-19 9.551507e-17 TRUE
## 11 2.701091e-22 1.353246e-19 TRUE
## 12 4.825991e-17 2.417822e-14 TRUE
## 13 3.152862e-14 1.579584e-11 TRUE
## 14 8.154777e-16 4.085543e-13 TRUE
## 15 1.924326e-17 9.640873e-15 TRUE
## 16 5.037341e-21 2.523708e-18 TRUE
## 17 5.553072e-21 2.782089e-18 TRUE
## 18 1.420912e-18 7.118772e-16 TRUE
## 19 2.606991e-15 1.306102e-12 TRUE
## 20 1.072896e-20 5.375207e-18 TRUE
## 21 1.509127e-30 7.560726e-28 TRUE
## 22 2.764991e-20 1.385261e-17 TRUE
## 23 9.453862e-18 4.736385e-15 TRUE
## 24 1.726259e-19 8.648557e-17 TRUE
## 25 3.935707e-21 1.971789e-18 TRUE
## 26 1.932797e-24 9.683313e-22 TRUE
## 27 1.155220e-23 5.787654e-21 TRUE
## 28 3.042083e-32 1.524084e-29 TRUE
## 29 7.826797e-29 3.921226e-26 TRUE
## 30 4.970143e-33 2.490042e-30 TRUE
## 31 2.575937e-26 1.290545e-23 TRUE
## 32 6.370050e-26 3.191395e-23 TRUE
## passed_ss_RegionScotland direct
## 1 TRUE Positive LFC
## 2 TRUE Positive LFC
## 3 TRUE Positive LFC
## 4 TRUE Positive LFC
## 5 TRUE Positive LFC
## 6 TRUE Positive LFC
## 7 TRUE Positive LFC
## 8 TRUE Positive LFC
## 9 TRUE Positive LFC
## 10 TRUE Positive LFC
## 11 TRUE Positive LFC
## 12 TRUE Negative LFC
## 13 TRUE Negative LFC
## 14 TRUE Negative LFC
## 15 TRUE Negative LFC
## 16 TRUE Negative LFC
## 17 TRUE Negative LFC
## 18 TRUE Negative LFC
## 19 TRUE Negative LFC
## 20 TRUE Negative LFC
## 21 TRUE Negative LFC
## 22 TRUE Negative LFC
## 23 TRUE Negative LFC
## 24 TRUE Negative LFC
## 25 TRUE Negative LFC
## 26 TRUE Negative LFC
## 27 TRUE Negative LFC
## 28 TRUE Negative LFC
## 29 TRUE Negative LFC
## 30 TRUE Negative LFC
## 31 TRUE Negative LFC
## 32 TRUE Negative LFC
# Make bar plot
dif.region.plot <- dif.region.df %>%
ggplot(aes(x = taxon, y = lfc_RegionScotland, fill = direct)) +
geom_bar(stat = "identity", width = 0.7, color = "black",
position = position_dodge(width = 0.4)) +
geom_errorbar(aes(ymin = lfc_RegionScotland - se_RegionScotland,
ymax = lfc_RegionScotland + se_RegionScotland),
width = 0.2, position = position_dodge(0.05),
color = "black") +
labs(x = "Genus", y = "Log fold change") +
ggtitle(label = "Differentially abundant taxa",
subtitle="Region: Scotland vs England") +
scale_fill_discrete(name = NULL) +
scale_color_discrete(name = NULL) +
theme_bw() +
theme(panel.grid.minor.y = element_blank(),
axis.text.x = element_text(size = 6, angle = 60, hjust = 1))
# Convert to plotly
dif.region.plot_plotly <- ggplotly(dif.region.plot, tooltip = "text")
dif.region.plot_plotly
# Save the plot as an HTML file
htmlwidgets::saveWidget(dif.region.plot_plotly, "results/plotly/differential_abundant_taxa_Region.html")
# Extract the first positive and negative taxa
first_positive_taxa.region <- dif.region.df %>%
filter(direct == "Positive LFC") %>%
slice(1) %>%
pull(taxon)
first_negative_taxa.region <- dif.region.df %>%
filter(direct == "Negative LFC") %>%
slice(1) %>%
pull(taxon)
# Generate and plot the box plots
positive_plot.region <- diff_abund.box_plot(first_positive_taxa.region, "Region",
paste("Abundance of", first_positive_taxa.region, "by Region"))
negative_plot.region <- diff_abund.box_plot(first_negative_taxa.region, "Region",
paste("Abundance of", first_negative_taxa.region, "by Region"))
print(positive_plot.region)
print(negative_plot.region)
# Compute unweighted UniFrac distance
unifrac_dist <- UniFrac(BacIndAgriGenera.rarefied, weighted = FALSE)
# Perform NMDS ordination
ordination.NMDS <- ordinate(BacIndAgriGenera.rarefied, method = "NMDS", distance = unifrac_dist)
## For 3d plotting
# NMDS ordination
ordination.NMDS.3d <- metaMDS(unifrac_dist, k=3)
# Extract NMDS samples scores (the position of each sample in the ordination space)
nmds_scores <- scores(ordination.NMDS.3d, display="sites")
# Convert to data frame and add sample metadata
nmds_data <- as.data.frame(nmds_scores)
###Region
# 2d plot
ordination_NMDS.Region <- plot_ordination(BacIndAgriGenera.rarefied, ordination.NMDS, type="samples", color="Region") +
ggtitle("samples ordination:",
subtitle="NMDS with unweighted UniFrac distances")+
geom_point(size=3) + stat_ellipse() +
theme_bw() +
theme(legend.position = "bottom") +
guides(color=guide_legend(ncol=3))
print(ordination_NMDS.Region)
nmds_data$Region <- sample_data(BacIndAgriGenera.rarefied)$Region
# 3D plot
ordination_NMDS.Region.3d <- plot_ly(nmds_data, x = ~NMDS1, y = ~NMDS2, z = ~NMDS3,
color = ~Region, colors = c("red", "blue"),
type = 'scatter3d', mode = 'markers',
marker = list(size = 5))
ordination_NMDS.Region.3d <- ordination_NMDS.Region.3d %>% layout(title = "NMDS with Unweighted UniFrac Distances",
scene = list(xaxis = list(title = 'NMDS1'),
yaxis = list(title = 'NMDS2'),
zaxis = list(title = 'NMDS3')))
ordination_NMDS.Region.3d
###FLU
ordination_NMDS.FLU <- plot_ordination(BacIndAgriGenera.rarefied, ordination.NMDS, type="samples", color="Former_landuse") +
ggtitle("samples ordination:",
subtitle="NMDS with unweighted UniFrac distances")+
geom_point(size=3) + stat_ellipse() +
theme_bw() +
theme(legend.position = "bottom") +
guides(color=guide_legend(ncol=3))
print(ordination_NMDS.FLU)
nmds_data$Former_landuse <- sample_data(BacIndAgriGenera.rarefied)$Former_landuse
# 3D plot
ordination_NMDS.FLU.3d <- plot_ly(nmds_data, x = ~NMDS1, y = ~NMDS2, z = ~NMDS3,
color = ~Former_landuse, colors = c("red", "blue"),
type = 'scatter3d', mode = 'markers',
marker = list(size = 5))
ordination_NMDS.FLU.3d <- ordination_NMDS.FLU.3d %>% layout(title = "NMDS with Unweighted UniFrac Distances",
scene = list(xaxis = list(title = 'NMDS1'),
yaxis = list(title = 'NMDS2'),
zaxis = list(title = 'NMDS3')))
ordination_NMDS.FLU.3d
# Convert BacIndAgriGenera.rarefied to data frame for abundance and environmental data
abund_data <- as.data.frame(t(otu_table(BacIndAgriGenera.rarefied)))
env_data <- data.frame(as(sample_data(BacIndAgriGenera.rarefied), "data.frame"))
# Select variables
env_data <- env_data[, c("Region", "Former_landuse", "Woodland_age", "pH", "Conductivity")]
# Make sure pH, age, and conductivity are numerical
env_data$Woodland_age <- as.numeric(as.character(env_data$Woodland_age))
env_data$Conductivity <- as.numeric(as.character(env_data$Conductivity))
env_data$pH <- as.numeric(as.character(env_data$pH))
# Remove NA values
env_data <- na.omit(env_data)
# Factorize categorical columns
env_data$Region <- as.factor(env_data$Region)
env_data$Former_landuse <- as.factor(env_data$Former_landuse)
# Select row names in abund_data that match env_data row names
abund_data <- abund_data[rownames(abund_data) %in% rownames(env_data), ]
# Perform RDA, constraining all env_data variables
ordination.RDA <- rda(abund_data ~ pH + Conductivity, data = env_data)
RDA.R2adj <- RsquareAdj(ordination.RDA)$adj.r.squared
RDA.R2adj
## [1] 0.277496
# # Enable the r-universe repo
# options(repos = c(
# fawda123 = 'https://fawda123.r-universe.dev',
# CRAN = 'https://cloud.r-project.org'))
#
# # Install ggord
# install.packages('ggord')
library(ggord)
ggord(ordination.RDA, env_data$Region, ext=0.8, txt=4, grp_title="Region", xlims=c(-1,1), ylims=c(-1,1), alpha_el=0.6, size=1.5, poly=FALSE,
vectyp="longdash", obslab=FALSE, ptslab=FALSE)
ggord(ordination.RDA, env_data$Former_landuse, ext=0.8, txt=4, grp_title="Former_landuse", xlims=c(-1,1), ylims=c(-1,1), poly=FALSE, size=1.5,
vectyp="longdash", obslab=FALSE, ptslab=FALSE)
# Perform CCA
ordination.cCA <- cca(abund_data ~ pH + Conductivity, data = env_data)
cCA.R2adj <- RsquareAdj(ordination.cCA)$adj.r.squared
cCA.R2adj
## [1] 0.1313167
ggord(ordination.cCA, env_data$Region, ext=0.8, txt=4, grp_title="Region", xlims=c(-1,1), ylims=c(-1,1), alpha_el=0.6, size=1.5,
vectyp="longdash", obslab=FALSE)
ggord(ordination.cCA, env_data$Former_landuse, ext=0.8, txt=4, grp_title="Former_landuse", xlims=c(-1,1), ylims=c(-1,1), alpha_el=0.6, size=1.5,
vectyp="longdash", obslab=FALSE)
# Re transpose abund_data
otu_table_matrix <- as.matrix(t(abund_data))
# Create a new phyloseq object with the filtered samples (based on env_data, no NaNs)
BacIndAgriGenera.rarefied_filtered <- phyloseq(
otu_table(otu_table_matrix, taxa_are_rows = TRUE),
sample_data(env_data),
tax_table(BacIndAgriGenera.rarefied),
phy_tree(BacIndAgriGenera.rarefied)
)
# Calculate beta diversity metrics
weighted_unifrac_dist.filtered <- UniFrac(BacIndAgriGenera.rarefied_filtered, weighted = TRUE)
unweighted_unifrac_dist.filtered <- UniFrac(BacIndAgriGenera.rarefied_filtered, weighted = FALSE)
bray_dist.filtered <- distance(BacIndAgriGenera.rarefied_filtered, method="bray")
adjus_p_value <- function(adonis_result){
# Extract p-values
p_values <- adonis_result$`Pr(>F)`
# Adjust the p-values
p_adjusted <- p.adjust(p_values)
# Add the adjusted p-values to adonis2 table
adonis_result$`Pr(>F)_adjusted` <- c(p_adjusted)
return(adonis_result)
}
library(scales)
nested_model.pie <- function(nested_model, title){
# Extract the explained variance (R2)
explained_variance <- nested_model$R2
# Remove the NA values associated with the rest of table info
p_values <- nested_model$`Pr(>F)`
explained_variance <- explained_variance[!is.na(p_values)]
# Names of the factors
factors <- rownames(nested_model)[1:length(explained_variance)]
# Create a data frame for plotting
plot_data <- data.frame(
Factor = factors,
ExplainedVariance = explained_variance
)
# Ensure the levels of Factor are in the same order as the explained variance
plot_data$Factor <- factor(plot_data$Factor, levels = plot_data$Factor)
# Custom legends
plot_data$LegendLabel <- paste0(plot_data$Factor,
" (", percent(plot_data$ExplainedVariance), ")")
# Create the pie chart
pie_chart <- ggplot(plot_data, aes(x = "", y = ExplainedVariance, fill = Factor)) +
geom_bar(width = 1, stat = "identity") +
coord_polar(theta = "y") +
scale_y_continuous(labels = percent_format()) +
theme_void() +
theme(legend.position = "right") +
labs(title = title, fill = "Factor") +
scale_fill_manual(values = scales::brewer_pal(palette = "Set3")(length(plot_data$Factor)),
labels = plot_data$LegendLabel)
print(pie_chart)
}
pH / conductivity / FLU / woodland age / region
nested_model.weighted_unifrac <- adonis2(weighted_unifrac_dist.filtered ~
pH + Region + Former_landuse + Woodland_age +
Region/Former_landuse +
Former_landuse/pH + Former_landuse/Conductivity +
Former_landuse/Woodland_age +
Region/Woodland_age,
data = env_data, permutations=1000)
nested_model.weighted_unifrac
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = weighted_unifrac_dist.filtered ~ pH + Region + Former_landuse + Woodland_age + Region/Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 0.040999 0.24264 147.0171 0.000999 ***
## Region 1 0.029401 0.17400 105.4309 0.000999 ***
## Former_landuse 1 0.008858 0.05242 31.7647 0.000999 ***
## Woodland_age 1 0.001770 0.01047 6.3463 0.000999 ***
## Region:Former_landuse 1 0.002565 0.01518 9.1994 0.000999 ***
## pH:Former_landuse 1 0.000681 0.00403 2.4405 0.045954 *
## Former_landuse:Conductivity 2 0.003073 0.01819 5.5098 0.000999 ***
## Former_landuse:Woodland_age 1 0.001544 0.00914 5.5365 0.001998 **
## Region:Woodland_age 1 0.000882 0.00522 3.1614 0.014985 *
## Residual 284 0.079199 0.46871
## Total 294 0.168972 1.00000
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
adjus_p_value(nested_model.weighted_unifrac)
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = weighted_unifrac_dist.filtered ~ pH + Region + Former_landuse + Woodland_age + Region/Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 0.040999 0.24264 147.0171 0.000999
## Region 1 0.029401 0.17400 105.4309 0.000999
## Former_landuse 1 0.008858 0.05242 31.7647 0.000999
## Woodland_age 1 0.001770 0.01047 6.3463 0.000999
## Region:Former_landuse 1 0.002565 0.01518 9.1994 0.000999
## pH:Former_landuse 1 0.000681 0.00403 2.4405 0.045954
## Former_landuse:Conductivity 2 0.003073 0.01819 5.5098 0.000999
## Former_landuse:Woodland_age 1 0.001544 0.00914 5.5365 0.001998
## Region:Woodland_age 1 0.000882 0.00522 3.1614 0.014985
## Residual 284 0.079199 0.46871
## Total 294 0.168972 1.00000
## Pr(>F)_adjusted
## pH 0.008991 **
## Region 0.008991 **
## Former_landuse 0.008991 **
## Woodland_age 0.008991 **
## Region:Former_landuse 0.008991 **
## pH:Former_landuse 0.045954 *
## Former_landuse:Conductivity 0.008991 **
## Former_landuse:Woodland_age 0.008991 **
## Region:Woodland_age 0.029970 *
## Residual
## Total
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
nested_model.pie(nested_model.weighted_unifrac, "Explained Variance of weighted UniFrac data by environmental factors")
nested_model.unweighted_unifrac <- adonis2(unweighted_unifrac_dist.filtered ~
pH + Region + Former_landuse + Woodland_age +
Region/Former_landuse +
Former_landuse/pH + Former_landuse/Conductivity +
Former_landuse/Woodland_age +
Region/Woodland_age,
data = env_data, permutations=1000)
nested_model.unweighted_unifrac
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = unweighted_unifrac_dist.filtered ~ pH + Region + Former_landuse + Woodland_age + Region/Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 2.1636 0.08721 33.1802 0.000999 ***
## Region 1 1.9040 0.07675 29.1987 0.000999 ***
## Former_landuse 1 0.6757 0.02724 10.3619 0.000999 ***
## Woodland_age 1 0.2368 0.00955 3.6321 0.000999 ***
## Region:Former_landuse 1 0.3844 0.01550 5.8957 0.000999 ***
## pH:Former_landuse 1 0.1812 0.00730 2.7791 0.001998 **
## Former_landuse:Conductivity 2 0.2890 0.01165 2.2163 0.001998 **
## Former_landuse:Woodland_age 1 0.2732 0.01101 4.1898 0.000999 ***
## Region:Woodland_age 1 0.1813 0.00731 2.7805 0.000999 ***
## Residual 284 18.5189 0.74648
## Total 294 24.8082 1.00000
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
adjus_p_value(nested_model.unweighted_unifrac)
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = unweighted_unifrac_dist.filtered ~ pH + Region + Former_landuse + Woodland_age + Region/Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 2.1636 0.08721 33.1802 0.000999
## Region 1 1.9040 0.07675 29.1987 0.000999
## Former_landuse 1 0.6757 0.02724 10.3619 0.000999
## Woodland_age 1 0.2368 0.00955 3.6321 0.000999
## Region:Former_landuse 1 0.3844 0.01550 5.8957 0.000999
## pH:Former_landuse 1 0.1812 0.00730 2.7791 0.001998
## Former_landuse:Conductivity 2 0.2890 0.01165 2.2163 0.001998
## Former_landuse:Woodland_age 1 0.2732 0.01101 4.1898 0.000999
## Region:Woodland_age 1 0.1813 0.00731 2.7805 0.000999
## Residual 284 18.5189 0.74648
## Total 294 24.8082 1.00000
## Pr(>F)_adjusted
## pH 0.008991 **
## Region 0.008991 **
## Former_landuse 0.008991 **
## Woodland_age 0.008991 **
## Region:Former_landuse 0.008991 **
## pH:Former_landuse 0.008991 **
## Former_landuse:Conductivity 0.008991 **
## Former_landuse:Woodland_age 0.008991 **
## Region:Woodland_age 0.008991 **
## Residual
## Total
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
nested_model.pie(nested_model.unweighted_unifrac, "Explained Variance of unweighted UniFrac data by environmental factors")
nested_model.bray <- adonis2(bray_dist.filtered ~ pH + Region*Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age,
data = env_data, permutations=1000)
nested_model.bray
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = bray_dist.filtered ~ pH + Region * Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 7.5751 0.24543 134.3360 0.000999 ***
## Region 1 3.3632 0.10896 59.6416 0.000999 ***
## Former_landuse 1 1.5583 0.05049 27.6348 0.000999 ***
## Region:Former_landuse 1 0.4590 0.01487 8.1395 0.000999 ***
## pH:Former_landuse 1 0.2356 0.00763 4.1788 0.003996 **
## Former_landuse:Conductivity 2 0.6036 0.01956 5.3522 0.000999 ***
## Former_landuse:Woodland_age 2 0.8841 0.02864 7.8392 0.000999 ***
## Region:Woodland_age 1 0.1711 0.00554 3.0341 0.017982 *
## Residual 284 16.0146 0.51887
## Total 294 30.8647 1.00000
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
adjus_p_value(nested_model.bray)
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = bray_dist.filtered ~ pH + Region * Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 7.5751 0.24543 134.3360 0.000999
## Region 1 3.3632 0.10896 59.6416 0.000999
## Former_landuse 1 1.5583 0.05049 27.6348 0.000999
## Region:Former_landuse 1 0.4590 0.01487 8.1395 0.000999
## pH:Former_landuse 1 0.2356 0.00763 4.1788 0.003996
## Former_landuse:Conductivity 2 0.6036 0.01956 5.3522 0.000999
## Former_landuse:Woodland_age 2 0.8841 0.02864 7.8392 0.000999
## Region:Woodland_age 1 0.1711 0.00554 3.0341 0.017982
## Residual 284 16.0146 0.51887
## Total 294 30.8647 1.00000
## Pr(>F)_adjusted
## pH 0.007992 **
## Region 0.007992 **
## Former_landuse 0.007992 **
## Region:Former_landuse 0.007992 **
## pH:Former_landuse 0.007992 **
## Former_landuse:Conductivity 0.007992 **
## Former_landuse:Woodland_age 0.007992 **
## Region:Woodland_age 0.017982 *
## Residual
## Total
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
nested_model.pie(nested_model.bray, "Explained Variance of bray curtis by environmental factors")
nested_model.abund <- adonis2(abund_data ~ pH + Region*Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age,
data = env_data, permutations=1000)
nested_model.abund
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = abund_data ~ pH + Region * Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 7.5751 0.24543 134.3360 0.000999 ***
## Region 1 3.3632 0.10896 59.6416 0.000999 ***
## Former_landuse 1 1.5583 0.05049 27.6348 0.000999 ***
## Region:Former_landuse 1 0.4590 0.01487 8.1395 0.000999 ***
## pH:Former_landuse 1 0.2356 0.00763 4.1788 0.000999 ***
## Former_landuse:Conductivity 2 0.6036 0.01956 5.3522 0.000999 ***
## Former_landuse:Woodland_age 2 0.8841 0.02864 7.8392 0.000999 ***
## Region:Woodland_age 1 0.1711 0.00554 3.0341 0.022977 *
## Residual 284 16.0146 0.51887
## Total 294 30.8647 1.00000
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
adjus_p_value(nested_model.abund)
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 1000
##
## adonis2(formula = abund_data ~ pH + Region * Former_landuse + Former_landuse/pH + Former_landuse/Conductivity + Former_landuse/Woodland_age + Region/Woodland_age, data = env_data, permutations = 1000)
## Df SumOfSqs R2 F Pr(>F)
## pH 1 7.5751 0.24543 134.3360 0.000999
## Region 1 3.3632 0.10896 59.6416 0.000999
## Former_landuse 1 1.5583 0.05049 27.6348 0.000999
## Region:Former_landuse 1 0.4590 0.01487 8.1395 0.000999
## pH:Former_landuse 1 0.2356 0.00763 4.1788 0.000999
## Former_landuse:Conductivity 2 0.6036 0.01956 5.3522 0.000999
## Former_landuse:Woodland_age 2 0.8841 0.02864 7.8392 0.000999
## Region:Woodland_age 1 0.1711 0.00554 3.0341 0.022977
## Residual 284 16.0146 0.51887
## Total 294 30.8647 1.00000
## Pr(>F)_adjusted
## pH 0.007992 **
## Region 0.007992 **
## Former_landuse 0.007992 **
## Region:Former_landuse 0.007992 **
## pH:Former_landuse 0.007992 **
## Former_landuse:Conductivity 0.007992 **
## Former_landuse:Woodland_age 0.007992 **
## Region:Woodland_age 0.022977 *
## Residual
## Total
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
nested_model.pie(nested_model.abund, "Explained Variance of abundance data by environmental factors")